/* Software Name : AsmDex
 * Version : 1.0
 *
 * Copyright © 2012 France Télécom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ow2.asmdex.structureWriter;

import java.util.ArrayList;
import java.util.List;

import org.ow2.asmdex.Opcodes;
import org.ow2.asmdex.encodedValue.EncodedValue;
import org.ow2.asmdex.encodedValue.EncodedValueArray;
import org.ow2.asmdex.encodedValue.EncodedValueFactory;

/**
 * Simple class representing one class_def_item, for the writer.
 * 
 * The equals and hashCode methods have been overridden in order to detect easily
 * duplicates in Sets. ONLY className is used to differentiate the classes.
 * 
 * Implements Comparable in order to easily sort the Classes. The test is minimalist is is only useful as
 * a first-pass sort. A second pass, after the Dex file is parsed, will be done to ensure SuperClass are
 * encoded before their children.
 * 
 * It is IMPORTANT to note that the encoded Fields/Methods are NOT sorted according to their ids, but in
 * the order of appearance of the fields/methods.
 * 
 * @author Julien Névo
 */
public class ClassDefinitionItem implements Comparable<ClassDefinitionItem>, IAnnotationsHolder {
	
	/**
	 * Name of the Class.
	 */
	final private String className;
	
	/**
	 * Access flags of the Class.
	 */
	final private int accessFlags;
	
	/**
	 * Name of the super class if any, or Null.
	 */
	final private String superClassName;
	
	/**
	 * TypeList containing the names of the interface if any, or Null.
	 */
	final private TypeList interfaceNames;
	
	/**
	 * The Constant Pool of the Application.
	 */
	final private ConstantPool constantPool;
	
	/**
	 * The source file that defines the Class.
	 */
	private String sourceFileName;
	
	/**
	 * The Signature of this Class, or Null if there is none. 
	 */
	final private String[] signature;
	
	/**
	 * The hashcode of the Class.
	 */
	final private int hashCode;
	
	/**
	 * Annotation_set_item, representing all the annotation_items for this Class.
	 * It concerns Class Annotations only, not Field, Method or Parameters Annotations.
	 */
	private AnnotationSetItem annotationSetItem = new AnnotationSetItem();
	
	// Fields of class_data_item.
	private ArrayList<Field> staticFields = new ArrayList<Field>();
	private ArrayList<Field> instanceFields = new ArrayList<Field>();
	
	// Methods of class_data_item.
	private ArrayList<Method> directMethods = new ArrayList<Method>();
	private ArrayList<Method> virtualMethods = new ArrayList<Method>();
	
	/**
	 * This may, if needed, contain an EncodedArray which will contain Encoded Values to Member Classes. It will be
	 * filled through the visitInnerClass method in ClassWriter. All this will allow the encoding of the
	 * dalvik.annotation.MemberClasses Annotation.
	 */
	private EncodedValueArray memberClassArray;
	
	/**
	 * The annotation_directory_item of the Class. It is only filled after the Class has been fully
	 * parsed, or stays to Null if no Annotation is present in the Class, or the Methods, Fields,
	 * Parameters it possesses.
	 */
	private AnnotationDirectoryItem annotationDirectoryItem;
	
	/**
	 * List of AnnotationItems which is <i>ONLY</i> used for Default Annotations. We may encounter
	 * several Default Annotation while parsing the <i>Methods</i>, but they must be encoded in the Class,
	 * and as <i>one</i> AnnotationElement which contains one Encoded Value of VALUE_ANNOTATION type,
	 * which contains all the AnnotationElement.
	 * The list is Null if not used. 
	 */
	private List<AnnotationItem> annotationItemsForDefaultAnnotation;
	
	/**
	 * Offset in bytes of the access_flags field in the class_def_item structure.
	 */
	final public static int ACCESS_FLAGS_OFFSET = 4;
	
	/**
	 * Offset in bytes of the superclass_idx field in the class_def_item structure.
	 */
	final public static int SUPERCLASS_IDX_OFFSET = 4 * 2;
	
	/**
	 * Offset in bytes of the interfaces_off field in the class_def_item structure.
	 */
	final public static int INTERFACES_OFFSET = 4 * 3; 
	
	/**
	 * Offset in bytes of the source_file_idx field in the class_def_item structure.
	 */
	final public static int SOURCE_FILE_IDX_OFFSET = 4 * 4;
	
	/**
	 * Offset in bytes of the annotations_off field in the class_def_item structure.
	 */
	final public static int ANNOTATIONS_OFF_OFFSET = 4 * 5; 
	
	/**
	 * Offset in bytes of the class_data_off field in the class_def_item structure.
	 */
	final public static int CLASS_DATA_OFF_OFFSET = 4 * 6;
	
	/**
	 * Constructor of a ClassDefinitionItem.
	 * @param className the name of the Class, fully qualified.
	 * @param superClassName the possible name of the super Class, or Null.
	 * @param accessFlags the access flags.
	 * @param interfaceNames names of the interface, or Null.
	 * @param signature the Signature of this Class, or Null if there is none. 
	 * @param constantPool the Constant Pool of this Application.
	 */
	public ClassDefinitionItem(String className, String superClassName, int accessFlags,
			TypeList interfaceNames, String[] signature, ConstantPool constantPool) {
		this.className = className;
		this.accessFlags = accessFlags;
		this.superClassName = superClassName;
		this.interfaceNames = interfaceNames;
		this.signature = signature;
		this.constantPool = constantPool;
		
		hashCode = className.hashCode();
	}

	
	// ----------------------------------------------
	// Getters and Setters.
	// ----------------------------------------------

	/**
	 * Returns the name of the Class.
	 * @return the name of the Class.
	 */
	public String getClassName() {
		return className;
	}
	
	/**
	 * Returns the Super Class of this Class. May be Null is the Class has no Super Class. 
	 * @return the Super Class of this Class, or Null.
	 */
	public String getSuperClassName() {
		return superClassName;
	}
	
	/**
	 * Returns the Interfaces used by the Class. The list it contains may be empty.
	 * @return the Interfaces used by the Class.
	 */
	public TypeList getInterfaces() {
		return interfaceNames;
	}
	
	/**
	 * Returns the access flags of the Class.
	 * @return the access flags of the Class.
	 */
	public int getAccessFlags() {
		return accessFlags;
	}
	
	/**
	 * Returns the source file that defines the Class. May be Null.
	 * @return the source file that defines the Class. May be Null.
	 */
	public String getSourceFileName() {
		return sourceFileName;
	}
	
	/**
	 * Sets the source file that defines the Class. May be Null.
	 *
	 */
	public void setSourceFileName(String sourceFileName) {
		this.sourceFileName = sourceFileName;
	}
	
	/**
	 * Returns the Signature of the Class. May be Null.
	 * @return the Signature of the Class. May be Null.
	 */
	public String[] getSignature() {
		return signature;
	}
	
	/**
	 * Returns the direct methods this class contains.
	 * @return the direct methods this class contains.
	 */
	public ArrayList<Method> getDirectMethods() {
		return directMethods;
	}
	
	/**
	 * Returns the virtual methods this class contains.
	 * @return the virtual methods this class contains.
	 */
	public ArrayList<Method> getVirtualMethods() {
		return virtualMethods;
	}
	
	/**
	 * Returns the static fields of this class.
	 * @return the static fields of this class.
	 */
	public ArrayList<Field> getStaticFields() {
		return staticFields;
	}

	/**
	 * Returns the instance fields of this class.
	 * @return the instance fields of this class.
	 */
	public ArrayList<Field> getInstanceFields() {
		return instanceFields;
	}
	
	/**
	 * Returns the number of direct methods this class currently contains.
	 * @return the number of direct methods this class currently contains.
	 */
	public int getNbDirectMethods() {
		return directMethods.size();
	}
	
	/**
	 * Returns the number of virtual methods this class currently contains.
	 * @return the number of virtual methods this class currently contains.
	 */
	public int getNbVirtualMethods() {
		return virtualMethods.size();
	}
	
	/**
	 * Returns the number of static fields this class currently contains.
	 * @return the number of static fields this class currently contains.
	 */
	public int getNbStaticFields() {
		return staticFields.size();
	}
	
	/**
	 * Returns the number of instance fields this class currently contains.
	 * @return the number of instance fields this class currently contains.
	 */
	public int getNbInstanceFields() {
		return instanceFields.size();
	}
	
	/**
	 * Returns the annotation_set_item this structure currently contains.
	 * @return the annotation_set_item this structure currently contains.
	 */
	@Override
	public AnnotationSetItem getAnnotationSetItem() {
		return annotationSetItem;
	}

	/**
	 * Returns the number of annotation_items this structure currently contains.
	 * @return the number of annotation_items this structure currently contains.
	 */
	@Override
	public int getNbAnnotations() {
		return annotationSetItem.getNbAnnotationItems();
	}
	
	/**
	 * Returns the count of annotated Methods, whether they are direct or virtual. This number is only set when
	 * parsing the Class is fully done.
	 * @return the count of annotated Methods.
	 */
	public int getNbAnnotatedMethods() {
		return annotationDirectoryItem == null ? 0 : annotationDirectoryItem.getNbAnnotatedMethods();
	}

	/**
	 * Returns the count of Methods that have annotated Parameters. This number is only set when
	 * parsing the Class is fully done.
	 * @return the count of Methods that have annotated Parameters.
	 */
	public int getNbMethodWithAnnotatedParameters() {
		return annotationDirectoryItem == null ? 0 : annotationDirectoryItem.getNbAnnotatedParameters();
	}
	
	/**
	 * Returns the count of annotated Fields, whether they are static or instance Fields.
	 * This number is only set when parsing the Class is fully done.
	 * @return the count of annotated Fields.
	 */
	public int getNbAnnotatedFields() {
		return annotationDirectoryItem == null ? 0 : annotationDirectoryItem.getNbAnnotatedFields();
	}
	
	/**
	 * Returns the Encoded Array containing the Member Classes, or null if none were added.
	 * @return the Encoded Array containing the Member Classes, or null if none were added.
	 */
	public EncodedValueArray getMemberClassArray() {
		return memberClassArray;
	}
	
	/**
	 * Returns the annotation_directory_item, filled after the whole Class has been parsed.
	 * It may be Null if no Annotation is present in the Class, or the Methods, Fields,
	 * Parameters it possesses.
	 * @return the annotation_directory_item, or Null.
	 */
	public AnnotationDirectoryItem getAnnotationDirectoryItem() {
		return annotationDirectoryItem;
	}
	
	/**
	 * Return the list of AnnotationItems containing each Default Annotation for this Class. It may be Null
	 * if none were found.
	 * @return the list of AnnotationItems, or Null.
	 */
	public List<AnnotationItem> getAnnotationItemsForDefaultAnnotation() {
		return annotationItemsForDefaultAnnotation;
	}
	
	
	// ----------------------------------------------
	// Public methods.
	// ----------------------------------------------
	
	/**
	 * Indicates if the Class is an Interface.
	 * @return true if the Class is an Interface.
	 */
	public boolean isInterface() {
		return (accessFlags & Opcodes.ACC_INTERFACE) != 0;
	}
	
	/**
	 * Adds a Method (direct or virtual) to the method list of the Class.
	 * @param method method to add.
	 */
	public void addMethod(Method method) {
		if (method.isVirtual()) {
			virtualMethods.add(method);
		} else {
			directMethods.add(method);
		}
	}
	
	/**
	 * Adds a Field to the Field list of the Class, whether is it a Static or Instance Field.
	 * @param field the field to add.
	 */
	public void addField(Field field) {
		if (field.isStatic()) {
			staticFields.add(field);
		} else {
			instanceFields.add(field);
		}
	}
	/**
	 * Adds an annotation_item to the annotations_set_items.
	 * @param annotationItem the Annotation Item to add.
	 */
	public void addAnnotationItem(AnnotationItem annotationItem) {
		annotationSetItem.addAnnotationItem(annotationItem);
	}
	
	/**
	 * Adds a Member Class Encoded Value to the Member Classes. They will be encoded as a
	 * dalvik.annotation.MemberClasses Annotation.
	 * @param memberClassType the value to add.
	 * @param constantPool the Constant Pool of the Application.
	 */
	public void addMemberClassValue(String memberClassType, ConstantPool constantPool) {
		if (memberClassArray == null) {
			memberClassArray = new EncodedValueArray();
		}
		
		constantPool.addTypeToConstantPool(memberClassType);
		
		// Creates an IElementValue and adds it to the Array.
		EncodedValue encodedValue = EncodedValueFactory.getEncodedValue(memberClassType, Opcodes.VALUE_TYPE);
		memberClassArray.addEncodedValue(encodedValue);
	}
	
	/**
	 * Adds an AnnotationItem to the list of AnnotationItem of Default Annotation. It must only be
	 * used in that case.
	 * @param annotationItem the AnnotationItem to add.
	 */
	public void addAnnotationItemForDefaultAnnotation(AnnotationItem annotationItem) {
		if (annotationItemsForDefaultAnnotation == null) {
			annotationItemsForDefaultAnnotation = new ArrayList<AnnotationItem>(1);
		}
		annotationItemsForDefaultAnnotation.add(annotationItem);
	}
	
	/**
	 * Builds the annotation_directory_item. This must be done only after the Class has been fully parsed.
	 * The structure is kept only if it possesses at least one annotation.
	 */
	public void buildAnnotationDirectoryItem() {
		AnnotationDirectoryItem adi = new AnnotationDirectoryItem();
		adi.setClassAnnotationSetItem(annotationSetItem);
		
		boolean foundAnnotation = (annotationSetItem.getNbAnnotationItems() > 0);
		
		foundAnnotation |= buildAnnotationDirectoryItemForMethods(directMethods, adi);
		foundAnnotation |= buildAnnotationDirectoryItemForMethods(virtualMethods, adi);
		
		for (Field field : staticFields) {
			if (field.getNbAnnotations() > 0) {
				adi.addAnnotatedField(field);
				foundAnnotation = true;
			}
		}
		// Tiny redundant code, not very important.
		for (Field field : instanceFields) {
			if (field.getNbAnnotations() > 0) {
				adi.addAnnotatedField(field);
				foundAnnotation = true;
			}
		}
		
		if (foundAnnotation) {
			annotationDirectoryItem = adi;
			constantPool.addAnnotationDirectoryItem(annotationDirectoryItem);
		}
	}
	
	/**
	 * Adds the Methods as annotated Methods if they have annotations. Should only been called
	 * by buildAnnotationDirectoryItem.
	 * @param methods a List of Methods, whether they are direct or virtual. 
	 * @param adi the AnnotationDirectoryItem to which to add the Methods.
	 * @return true if at least an annotation was found.
	 */
	private boolean buildAnnotationDirectoryItemForMethods(List<Method> methods, AnnotationDirectoryItem adi) {
		boolean foundAnnotation = false;
		for (Method method : methods) {
			if (method.getNbAnnotations() > 0) {
				adi.addAnnotatedMethods(method);
				foundAnnotation = true;
			}
			if (method.getNbParameterAnnotations() > 0) {
				adi.addAnnotatedParameter(method.getAnnotatedParameterSetRefList());
				foundAnnotation = true;
			}
		}
		
		return foundAnnotation;
	}
	
	// ----------------------------------------------
	// Overridden methods.
	// ----------------------------------------------
	
	@Override
	public int hashCode() {
		return hashCode;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}

		if (obj instanceof ClassDefinitionItem) {
			ClassDefinitionItem cdi = (ClassDefinitionItem)obj;
			return className.equals(cdi.className);
		}
		
		return false;
	}

	@Override
	public int compareTo(ClassDefinitionItem cdi) {
		if (this == cdi) {
			return 0;
		}
		
		// Tests the class name. This test is minimalist is is only useful as a first-pass sort.
		// A second pass, after the Dex file is parsed, will be done to ensure SuperClass are encoded
		// before their children.
		return className.compareTo(cdi.className);
	}






}
